{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Methods vs Functions\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We call methods using s.f() rather than f(s):\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = 'This could be any string!'\n",
    "\n",
    "print(len(s))     # len is a function\n",
    "\n",
    "print(s.upper())  # upper is a string method, called using the . notation\n",
    "                  # we say that we \"call the method upper on the string s\"\n",
    "\n",
    "print(s.replace('could', 'may')) # some methods take additional arguments"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See how we get different errors for improperly calling methods vs functions:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n = 123\n",
    "print(len(n))    # TypeError: object of type 'int' has no len()\n",
    "                 # This means that len() cannot work properly with int's\n",
    "\n",
    "n = 123\n",
    "print(n.upper()) # AttributeError: 'int' object has no attribute 'upper'\n",
    "                 # This means that there is no method upper() for int's"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Classes and Instances\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Classes are also called \"Types\" in Python.\n",
    "For example, these are classes: int, float, str, bool\n",
    "Instances are values of a given class or type.\n",
    "For example: 'abc' is a str instance (also called a string)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Objects and Object-Oriented Programming (OOP)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Every value in Python is an Object.\n",
    "Every instance is an object, and its type is some class.\n",
    "Every class is an object, too (its type is type!).\n",
    "That is why we call this Object-Oriented Programming\n",
    "We are using objects only a little bit now.\n",
    "Soon we will write our own classes.\n",
    "Then we will add some sophistication to how we write and use classes and objects.\n",
    "Even so, because we are using objects now, we are already using Object-Oriented Programming (OOP)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Writing Classes\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create our own class:\n",
    "class Dog:\n",
    "    # a class must have a body, even if it does nothing, so we will\n",
    "    # use 'pass' for now...\n",
    "    pass\n",
    "\n",
    "# Create instances of our class:\n",
    "d1 = Dog()\n",
    "d2 = Dog()\n",
    "\n",
    "# Verify the type of these instances:\n",
    "print(type(d1))             # Dog (actually, class '__main__.Dog')\n",
    "print(isinstance(d2, Dog))  # True\n",
    "\n",
    "# Set and get properties (aka 'fields' or 'attributes') of these instances:\n",
    "d1.name = 'Dot'\n",
    "d1.age = 4\n",
    "d2.name = 'Elf'\n",
    "d2.age = 3\n",
    "print(d1.name, d1.age) # Dot 4\n",
    "print(d2.name, d2.age) # Elf 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Writing Constructors\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Constructors let us pre-load our new instances with properties.\n",
    "This lets us write code like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1 = Dog('fred', 4) # now d1 is a Dog instance with name 'fred' and age 4\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We would like to write our constructor like this:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def constructor(dog, name, age):\n",
    "    # pre-load the dog instance with the given name and age:\n",
    "    dog.name = name\n",
    "    dog.age = age"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Unfortunately, Python does not use 'constructor' as the constructor name. Instead, it uses '__init__' (sorry about that), like so:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def __init__(dog, name, age):\n",
    "    # pre-load the dog instance with the given name and age:\n",
    "    dog.name = name\n",
    "    dog.age = age"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Also, unfortunately, while we could name the instance 'dog' like we did, standard convention requires that we name it 'self' (sorry again), like so:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def __init__(self, name, age):\n",
    "    # pre-load the dog instance with the given name and age:\n",
    "    self.name = name\n",
    "    self.age = age"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we place this method inside the class and we have a constructor that we can use, like so:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "    def __init__(self, name, age):\n",
    "        # pre-load the dog instance with the given name and age:\n",
    "        self.name = name\n",
    "        self.age = age\n",
    " \n",
    "# Create instances of our class, using our new constructor\n",
    "d1 = Dog('Dot', 4)\n",
    "d2 = Dog('Elf', 3)\n",
    "\n",
    "print(d1.name, d1.age) # Dot 4\n",
    "print(d2.name, d2.age) # Elf 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Writing Methods\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Start with a function:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "    def __init__(self, name, age):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "\n",
    "# Here is a function we will turn into a method:\n",
    "def sayHi(dog):\n",
    "    print(f'Hi, my name is {dog.name} and I am {dog.age} years old!')\n",
    "\n",
    "d1 = Dog('Dot', 4)\n",
    "d2 = Dog('Elf', 3)\n",
    "\n",
    "sayHi(d1) # Hi, my name is Dot and I am 4 years old!\n",
    "sayHi(d2) # Hi, my name is Elf and I am 3 years old!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Turn the function into a method, and the function call into a method call, like this:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "    def __init__(self, name, age):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "\n",
    "    # Now it is a method (simply by indenting it inside the class!)\n",
    "    def sayHi(dog):\n",
    "        print(f'Hi, my name is {dog.name} and I am {dog.age} years old!')\n",
    "\n",
    "d1 = Dog('Dot', 4)\n",
    "d2 = Dog('Elf', 3)\n",
    "\n",
    "# Notice how we change the function calls into method calls:\n",
    "\n",
    "d1.sayHi() # Hi, my name is Dot and I am 4 years old!\n",
    "d2.sayHi() # Hi, my name is Elf and I am 3 years old!1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, use self, as convention requires:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "    def __init__(self, name, age):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "\n",
    "    # Now we are using self, as convention requires:\n",
    "    def sayHi(self):\n",
    "        print(f'Hi, my name is {self.name} and I am {self.age} years old!')\n",
    "\n",
    "d1 = Dog('Dot', 4)\n",
    "d2 = Dog('Elf', 3)\n",
    "\n",
    "# Notice how we change the function calls into method calls:\n",
    "\n",
    "d1.sayHi() # Hi, my name is Dot and I am 4 years old!\n",
    "d2.sayHi() # Hi, my name is Elf and I am 3 years old!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Methods can take additional parameters, like so:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "    def __init__(self, name, age):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "\n",
    "    # This method takes a second parameter -- times\n",
    "    def bark(self, times):\n",
    "        print(f'{self.name} says: {\"woof!\" * times}')\n",
    "\n",
    "d = Dog('Dot', 4)\n",
    "\n",
    "d.bark(1) # Dot says: woof!\n",
    "d.bark(4) # Dot says: woof!woof!woof!woof!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Methods can also set properties, like so:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "    def __init__(self, name, age):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "        self.woofCount = 0   # we initialize the property in the constructor!\n",
    "\n",
    "    def bark(self, times):\n",
    "        # Then we can set and get the property in this method\n",
    "        self.woofCount += times\n",
    "        print(f'{self.name} says: {\"woof!\" * times} ({self.woofCount} woofs!)')\n",
    "\n",
    "d = Dog('Dot', 4)\n",
    "\n",
    "d.bark(1) # Dot says: woof! (1 woofs!)\n",
    "d.bark(4) # Dot says: woof!woof!woof!woof! (5 woofs!)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Advantages of Classes and Methods\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Encapsulation\n",
    "Organizes code\n",
    "A class includes the data and methods for that class.\n",
    "Promotes intuitive design\n",
    "Well-designed classes should be intuitive, so the data and methods in the class match commonsense expectations.\n",
    "Restricts access\n",
    "len is a function, so we can call len(True) (which crashes)\n",
    "upper is a method on strings but not booleans, so we cannot even call True.upper()\n",
    "Polymorphism\n",
    "Polymorphism: the same method name can run different code based on type, like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "    def speak(self):\n",
    "        print('woof!')\n",
    "\n",
    "class Cat:\n",
    "    def speak(self):\n",
    "        print('meow!')\n",
    "\n",
    "for animal in [ Dog(), Cat() ]:\n",
    "    animal.speak() # same method name, but one woofs and one meows!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Objects and Aliases\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Objects are mutable so aliases change!\n",
    "# Run this with the visualizer to make it clear!\n",
    "\n",
    "import copy\n",
    "\n",
    "class Dog:\n",
    "    def __init__(self, name, age, breed):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "        self.breed = breed\n",
    "\n",
    "dog1 = Dog('Dino', 10, 'shepherd')\n",
    "dog2 = dog1            # this is an alias\n",
    "dog3 = copy.copy(dog1) # this is a copy, not an alias\n",
    "\n",
    "dog1.name = 'Spot'\n",
    "print(dog2.name) # Spot (the alias changed, since it is the same object)\n",
    "print(dog3.name) # Dino (the copy did not change, since it is a different object)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Example: Animations with OOP\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is an updated version of the adding-and-deleting dots demo from here. This version adds methods to the Dot class so each dot is responsible for its own drawing and mouse handling.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from cmu_112_graphics import *\n",
    "import random\n",
    "\n",
    "class Dot:\n",
    "    def __init__(self, cx, cy, r, counter, color):\n",
    "        self.cx = cx\n",
    "        self.cy = cy\n",
    "        self.r = r\n",
    "        self.counter = counter\n",
    "        self.color = color\n",
    "\n",
    "    def redraw(self, app, canvas):\n",
    "        # Only redraw this dot\n",
    "        canvas.create_oval(self.cx-self.r, self.cy-self.r,\n",
    "                           self.cx+self.r, self.cy+self.r,\n",
    "                           fill='white', outline=self.color, width=15)\n",
    "        canvas.create_text(self.cx, self.cy, text=str(self.counter),\n",
    "                           fill='black')\n",
    "\n",
    "    def containsPoint(self, x, y):\n",
    "        return (((self.cx - x)**2 + (self.cy - y)**2)**0.5 <= self.r)\n",
    "\n",
    "    def mousePressed(self, event):\n",
    "        # We are guaranteed (event.x, event.y) is in this dot\n",
    "        self.counter += 1\n",
    "        self.color = getRandomColor()\n",
    "\n",
    "    def timerFired(self, app):\n",
    "        self.counter += 1\n",
    "\n",
    "def appStarted(app):\n",
    "    app.dots = [ ]\n",
    "    app.timerDelay = 1000 # once per second\n",
    "\n",
    "def getRandomColor():\n",
    "    colors = ['red', 'orange', 'yellow', 'green', 'blue', 'pink',\n",
    "              'lightGreen', 'gold', 'magenta', 'maroon', 'salmon',\n",
    "              'cyan', 'brown', 'orchid', 'purple']\n",
    "    return random.choice(colors)\n",
    "\n",
    "def mousePressed(app, event):\n",
    "    # go through dots in reverse order so that\n",
    "    # we find the topmost dot that intersects\n",
    "    for dot in reversed(app.dots):\n",
    "        if dot.containsPoint(event.x, event.y):\n",
    "            dot.mousePressed(event)\n",
    "            return\n",
    "    # mouse click was not in any dot, so create a new dot\n",
    "    newDot = Dot(cx=event.x, cy=event.y, r=20, counter=0, color='cyan')\n",
    "    app.dots.append(newDot)\n",
    "\n",
    "def keyPressed(app, event):\n",
    "    if (event.key == 'd'):\n",
    "        if (len(app.dots) > 0):\n",
    "            app.dots.pop(0)\n",
    "        else:\n",
    "            print('No more dots to delete!')\n",
    "\n",
    "def timerFired(app):\n",
    "    for dot in app.dots:\n",
    "        dot.timerFired(app)\n",
    "\n",
    "def redrawAll(app, canvas):\n",
    "    for dot in app.dots:\n",
    "        dot.redraw(app, canvas)\n",
    "\n",
    "    # draw the text\n",
    "    canvas.create_text(app.width/2, 20,\n",
    "                       text='Example: Adding and Deleting Shapes',\n",
    "                       fill='black')\n",
    "    canvas.create_text(app.width/2, 40,\n",
    "                       text='Mouse clicks outside dots create new dots',\n",
    "                       fill='black')\n",
    "    canvas.create_text(app.width/2, 60,\n",
    "                       text='Mouse clicks inside dots increase their counter',\n",
    "                       fill='black')\n",
    "    canvas.create_text(app.width/2, 70,\n",
    "                       text='and randomize their color.', fill='black')\n",
    "    canvas.create_text(app.width/2, 90,\n",
    "                       text='Pressing \"d\" deletes circles', fill='black')\n",
    "\n",
    "runApp(width=400, height=400)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "p2s",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.9.15"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
